Skip to content

gl: add --icaptcha-proof flag to register command#193

Open
jilt wants to merge 2 commits into
Gitlawb:mainfrom
jilt:feat/gl-icaptcha-proof-register
Open

gl: add --icaptcha-proof flag to register command#193
jilt wants to merge 2 commits into
Gitlawb:mainfrom
jilt:feat/gl-icaptcha-proof-register

Conversation

@jilt

@jilt jilt commented Jul 12, 2026

Copy link
Copy Markdown

This allows users to provide a pre-obtained iCaptcha proof token when registering with a node, sent as the x-icaptcha-proof header on the initial POST /api/register request.

Usage:
gl register --icaptcha-proof "<token>"
GITLAWB_ICAPTCHA_PROOF="<token>" gl register

The existing automatic iCaptcha retry flow (403 -> solve -> retry) remains unchanged and is still available if no initial proof is provided.

Summary

Add an optional --icaptcha-proof / GITLAWB_ICAPTCHA_PROOF argument to gl register that sends the proof as the x-icaptcha-proof header on the initial /api/register request.

This is a minimal compatibility fix for nodes running with icaptcha enforcement, without yet implementing the full automatic challenge/solve/retry flow.

Motivation & context

When registering an agent with a node that has iCaptcha enforcement enabled, the current gl register command has no way to provide a pre-obtained proof token. This forces users to either:

Disable iCaptcha on the node (reduces security), or
Manually modify the CLI source to add the header

issue #190

Kind of change

  • Bug fix
  • [ x] Feature
  • Security fix
  • Docs
  • Tests / CI
  • Refactor (no behavior change)
  • Breaking or protocol change (issue required first)

What changed

crates/gl/src/register.rs

  • Added icaptcha_proof: Option field to RegisterArgs
  • Changed the POST call from client.post(...) to client.post_with_proof(...) to attach the proof header

crates/gl/src/http.rs

  • Added pub async fn post_with_proof(...) method to NodeClient
  • Refactored send_signed() to call a new send_signed_with_proof() helper that accepts an optional initial proof
  • The existing automatic iCaptcha retry flow remains intact for 403 challenges

How a reviewer can verify

replace the crates, fmt and build then run gl register --icaptcha-proof "your-proof" a new proof from the api/register/ endpoint (i used postman to get the process done)

Before you request review

  • Scope is one logical change; no unrelated churn
  • cargo test --workspace passes locally
  • New behavior is covered by tests (required for fixes)
  • cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings are clean
  • Commit titles use Conventional Commits (feat(...), fix(...), docs(...))
  • Docs / .env.example updated if behavior or config changed (or N/A)
  • Checked existing PRs so this isn't a duplicate

Notes for reviewers

simplify the process of registering new agents without touching your codebase

Summary by CodeRabbit

  • New Features
    • Registration now supports supplying an optional iCaptcha proof through a command-line option or environment variable.
    • Provided proofs are included with the initial registration request.
    • Registration continues to handle iCaptcha challenges automatically when no proof is provided.
  • Bug Fixes
    • Improved registration request error messages based on whether a proof was supplied.
    • Updated success output formatting after registration.

This allows users to provide a pre-obtained iCaptcha proof token when
registering with a node, sent as the x-icaptcha-proof header on the
initial POST /api/register request.

Usage:
  gl register --icaptcha-proof "<token>"
  GITLAWB_ICAPTCHA_PROOF="<token>" gl register

The existing automatic iCaptcha retry flow (403 -> solve -> retry) remains
unchanged and is still available if no initial proof is provided.
@github-actions github-actions Bot added needs-issue PR has no linked issue needs-tests Source changed without accompanying tests (advisory) labels Jul 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the contribution. A couple of things will help us review this faster:

  • Link the issue this addresses (Closes #123). For protocol changes, open an issue first.
  • This changes Rust source but no tests changed. Tests are required for fixes and strongly encouraged for features.

See CONTRIBUTING.md. Update the PR and these notes will clear automatically.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bc81a1ed-fc40-4479-a9c1-2860d3705eb2

📥 Commits

Reviewing files that changed from the base of the PR and between d454de4 and 31ef12f.

📒 Files selected for processing (1)
  • crates/gl/src/register.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/gl/src/register.rs

📝 Walkthrough

Walkthrough

Registration now accepts an optional iCaptcha proof from CLI or environment configuration and sends it through a proof-aware signed POST flow. Existing iCaptcha challenge detection, solving, and retry behavior remains available.

Changes

iCaptcha registration support

Layer / File(s) Summary
Proof-aware signed HTTP requests
crates/gl/src/http.rs
NodeClient adds post_with_proof, and signed requests initialize proof state from optional input before using the existing retry flow.
Registration proof wiring and validation
crates/gl/src/register.rs
RegisterArgs exposes CLI and environment configuration, /api/register forwards the proof, output formatting is updated, and tests verify the header and UCAN creation.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant RegisterArgs
  participant NodeClient
  participant NodeAPI
  CLI->>RegisterArgs: Read iCaptcha proof
  RegisterArgs->>NodeClient: post_with_proof(/api/register, body, proof)
  NodeClient->>NodeAPI: Send signed POST with proof
  NodeAPI-->>NodeClient: Return response or iCaptcha challenge
  NodeClient->>NodeAPI: Solve challenge and retry when required
Loading

Possibly related issues

  • Gitlawb/node issue 190 — Implements the same registration proof argument and proof-aware request flow.

Possibly related PRs

  • Gitlawb/node#108 — Adds server-side iCaptcha header verification corresponding to this client-side proof submission.
  • Gitlawb/node#168 — Covers iCaptcha proof attachment and signed-request solve-and-retry behavior.

Suggested labels: sev:medium, kind:feature, crate:gl

Suggested reviewers: jatmn, beardthelion, kevincodex1

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding an iCaptcha proof flag to gl register.
Description check ✅ Passed The description follows the template well, covering summary, motivation, change type, implementation details, verification, and reviewer notes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/gl/src/register.rs (1)

148-156: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test that verifies the proof header is forwarded.

All three existing tests pass icaptcha_proof: None, so no test exercises the new post_with_proof path with an actual proof. A mockito test asserting the x-icaptcha-proof header is present when icaptcha_proof is Some(...) would protect this new feature path against regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/gl/src/register.rs` around lines 148 - 156, Add a register test near
the existing run/RegisterArgs tests that sets icaptcha_proof to Some(...) and
configures the mock server to assert the x-icaptcha-proof header value, thereby
exercising the post_with_proof path while preserving the existing no-proof
coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/gl/src/register.rs`:
- Around line 148-156: Add a register test near the existing run/RegisterArgs
tests that sets icaptcha_proof to Some(...) and configures the mock server to
assert the x-icaptcha-proof header value, thereby exercising the post_with_proof
path while preserving the existing no-proof coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b6f3668d-c02b-4b0a-bbe5-e4396b941cef

📥 Commits

Reviewing files that changed from the base of the PR and between ad7c2b2 and d454de4.

📒 Files selected for processing (2)
  • crates/gl/src/http.rs
  • crates/gl/src/register.rs

@github-actions github-actions Bot removed the needs-issue PR has no linked issue label Jul 12, 2026
@beardthelion beardthelion added the crate:gl gl — the contributor CLI label Jul 12, 2026

@beardthelion beardthelion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the client change end to end against a mock node, driving both the granted and the denied/hostile paths. The core is sound and the trust boundary is respected: a pre-supplied proof rides only the first request and does not change the retry budget (1 initial + MAX_ICAPTCHA_RETRIES), a stale token is discarded and re-solved rather than looped, a malformed proof value is rejected at header build instead of being sent, and a bogus proof grants nothing the automatic solve path doesn't since the node re-verifies the iCaptcha signature and the sub/DID binding. Requesting changes for one missing test on the new path plus two minor cleanups; none of these are correctness or security blockers.

What I verified by execution (both directions): valid proof forwards on the first request with zero solves; a bogus proof against an always-403 node stays bounded at exactly 3 requests / 2 solves, identical to the no-proof path; the retry carries the freshly solved proof, not the stale user token; Some("") sends a present-but-empty header (distinct from the omitted None path); and CR/LF/NUL in the token fail with builder error: failed to parse header value rather than being sent.

Findings

  • [P3] Add a test that the proof is forwarded on the first request. crates/gl/src/register.rs:57 / crates/gl/src/http.rs:119 — the new post_with_proof -> send_signed_with_proof(initial_proof) branch ships with no coverage: all three register fixtures pass icaptcha_proof: None, and every http.rs test drives send_signed (the None path), so nothing exercises a Some(proof) value reaching the first request. A regression that dropped or mis-threaded the initial proof would stay green. A drop-in that mirrors the existing test_register_success_saves_ucan setup:

    #[tokio::test]
    async fn register_forwards_icaptcha_proof_header() {
        let dir = TempDir::new().unwrap();
        write_identity(&dir);
        let mut server = mockito::Server::new_async().await;
        let m = server
            .mock("POST", "/api/register")
            .match_header("x-icaptcha-proof", "pre.solved.token")
            .with_status(200)
            .with_header("content-type", "application/json")
            .with_body(r#"{"message":"Welcome","ucan":"eyJhbGci.test.token","trust_score":0.5,"expires":"2026-12-31"}"#)
            .expect(1)
            .create_async()
            .await;
    
        run(RegisterArgs {
            node: server.url(),
            capabilities: vec!["git:push".to_string()],
            model: None,
            dir: Some(dir.path().to_path_buf()),
            icaptcha_proof: Some("pre.solved.token".to_string()),
        })
        .await
        .unwrap();
    
        m.assert();
        assert!(dir.path().join("ucan.json").exists());
    }

    I ran this against the branch and it passes; flipping the matched header value makes it fail, so it is load-bearing.

  • [P3] Document the --icaptcha-proof flag. crates/gl/src/register.rs:20 — the field carries #[arg(long, env = "GITLAWB_ICAPTCHA_PROOF")] but no /// doc comment, unlike every sibling in RegisterArgs. clap renders the field doc as the flag's help text, so gl register --help lists --icaptcha-proof <ICAPTCHA_PROOF> with an empty description. One line describing that it accepts a pre-obtained iCaptcha proof token fixes it.

  • [P3] Don't report a malformed proof as a connection failure. crates/gl/src/register.rs:59 — a token containing bytes outside the header grammar fails at header build, but .context("failed to connect to node") surfaces it as failed to connect to node: ... builder error: failed to parse header value, which points the user at their network rather than their input. Reachable now that the token is user-supplied. Either validate/trim the token at the CLI boundary or give this call site a context that doesn't assert a connection error.

The rest checks out: post/put/delete are behaviorally unchanged (they delegate with None), the full gl suite stays green, and the PR body's claim that the automatic retry flow is unchanged is accurate.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found issues that need to be addressed before this is ready.

Findings

  • [P3] Add help text for the new proof flag
    crates/gl/src/register.rs:20
    The field has no doc comment, so Clap renders --icaptcha-proof <ICAPTCHA_PROOF> with an empty description (only the environment-variable annotation). This is the only CLI discovery surface for the newly added user-facing token input, leaving users unable to tell what the flag expects. Add a concise description that it accepts a pre-obtained iCaptcha proof token.

  • [P3] Cover the supplied-proof registration path
    crates/gl/src/register.rs:57
    Every register::tests fixture sets icaptcha_proof: None; the existing http.rs proof test exercises the lower-level send_once helper rather than RegisterArgs -> post_with_proof. A regression that drops or misthreads the new CLI value would therefore leave the registration suite green and make the advertised workaround fail. Add a mocked registration test with Some(...) that matches the initial request's x-icaptcha-proof header.

  • [P3] Do not label malformed proof input as a connection failure
    crates/gl/src/register.rs:57
    A proof containing CR/LF is rejected locally while Reqwest builds the header, before any request is sent, but the surrounding context reports it as failed to connect to node. This new user-controlled error path sends users to debug node/network connectivity instead of the supplied token. Validate the proof at the CLI boundary or use neutral request context so invalid header values are reported accurately.

@jilt

jilt commented Jul 13, 2026

Copy link
Copy Markdown
Author

Thanks for the thorough review, @beardthelion and @jatmn.

I've addressed all three [P3] items:

  • Test coverage: Added register_forwards_icaptcha_proof_header in crates/gl/src/register.rs, which asserts that a supplied --icaptcha-proof value is forwarded as the x-icaptcha-proof header on the first request. A regression that dropped or mis-threaded the initial proof would now fail this test.

  • Documentation: Added a doc comment for the icaptcha_proof field in RegisterArgs, so gl register --help now describes --icaptcha-proof as: “Pre-obtained iCaptcha proof token to send on the first request.”

  • Error messaging: Updated the post_with_proof call in register.rs to use a more specific error context (failed to send registration request (check --icaptcha-proof value)) so malformed tokens are not reported as generic connection failures.

The HTTP client (crates/gl/src/http.rs) already ensures that:

  • The user-supplied proof is used only on the first request.
  • On a 403 iCaptcha challenge, retries use a freshly solved proof, not the stale user token.
  • All existing post/put/delete paths are behaviorally unchanged.

All local tests pass. Ready for another look whenever you have time.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found issues that need to be addressed before this is ready.

Findings

  • [P1] Do not forward a supplied proof across redirects
    crates/gl/src/http.rs:25-30
    NodeClient uses Reqwest's default redirect policy, so a 307/308 response to the new initial proof-bearing POST /api/register resends the x-icaptcha-proof header to the Location origin. Reqwest only removes its small built-in authorization/cookie header set on cross-host redirects; this custom proof header (and the signed request headers) remains attached. This expands a user-supplied proof beyond the configured node and lets a redirecting endpoint expose a fresh, DID-bound proof and replayable registration request to an arbitrary origin. Disable redirects for signed/proof-bearing writes (or explicitly reject cross-origin redirects) and cover that behavior.

  • [P1] Restore compilation of the existing registration tests
    crates/gl/src/register.rs:151
    Adding the required icaptcha_proof field left the existing RegisterArgs literals at lines 151, 183, and 202 incomplete. As a result, cargo test -p gl register fails to compile with E0063 before any test runs, including the new proof-forwarding test. Add icaptcha_proof: None to the pre-existing fixtures (and update any other direct initializers).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

crate:gl gl — the contributor CLI needs-tests Source changed without accompanying tests (advisory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants